/*
 * file:  expr.cpp (implementation of Expr).
 * Author: Jeremy Johnson 
 * Date: 1/31/06
 *
 * Description: 
 */

#include <iostream>
#include <string>
#include <map>
#include <list>
#include <sstream>
#include "program.h"

using namespace std;

// RAMInstruction is one instruction in the RAM instruction set
// This could have 0 or 1 operands (HLT, for example, has no operands)
RAMInstruction::RAMInstruction(Operators oper, string operand, int lineNumber, Types type)
{
	lineNumber_ = lineNumber;
	oper_ = oper;
	operand_ = operand;
	type_ = type;
}

void RAMInstruction::setLinked(Operators oper, int operand) 
{
	linkedoper_ = oper;
	linkedoperand_ = operand;
}	

// Returns this instruction's line number
int RAMInstruction::getLineNumber() 
{
	return lineNumber_;
}

// Sets this instruction's line number
int RAMInstruction::setLineNumber(int LnNum) 
{
	lineNumber_ = LnNum;
	return lineNumber_;
}

// Returns this instruction's operator
Operators RAMInstruction::getOper() 
{
	return oper_;
}

// Returns this instruction's type
Types RAMInstruction::getType() 
{
	return type_;
}

// Returns this instruction's operator
Operators RAMInstruction::getLinkedOper() 
{
	return linkedoper_;
}

// Returns this instruction's operator
int RAMInstruction::getLinkedOperand() 
{
	return linkedoperand_;
}

// Sets the constant value for this instruction
void RAMInstruction::setConstantValue(string constantvalue) 
{
	constantvalue_ = constantvalue;
}

// Sets the constant value for this instruction
string RAMInstruction::getConstantValue() 
{
	return constantvalue_;
}

// Returns this instructions operator as a string
string RAMInstruction::getOperString() 
{
	string operatorString[15] = { "LD" , "ST", "ADD", "SUB" , "JMN" , "JMZ",
		"HLT", "LBL", "JMP", "MUL" , "LDI" , "LDA" , "STI", "STA" , "UNK" };
	return operatorString[oper_];
}

// Returns this instructions operator as a string
string RAMInstruction::getLinkedOperString() 
{
	string operatorString[15] = { "LD" , "ST", "ADD", "SUB" , "JMN" , "JMZ",
		"HLT", "LBL", "JMP", "MUL" , "LDI" , "LDA" , "STI", "STA" , "UNK" };
	return operatorString[linkedoper_];
}

// Returns this instruction's operand
string RAMInstruction::getOperand() 
{
	return operand_;
}

// RAMCounter is a collection of counters to keep
// a running tally of used constants and temporary variables
// instructions_ is the list of instructions
// processed so far
RAMCounter::RAMCounter()
{
	list<RAMInstruction> instructions_;
	constant_ = 1;
	temporary_ = 1;
	label_ = 1;
	lineCounter_ = 1;
	constantTable_.clear();
	localTable_.clear();
	tempTable_.clear();
	labelTable_.clear();
	ConstantValueTable_.clear();
}

// Get the next available constant and then increment it
// If constant_ = 4, then this would return C4 and then 
// the next call would return C5, and so on.
string RAMCounter::getNextConstant(int value) 
{
	stringstream ss;
	if(constant_ < 10)
		ss << "0" << constant_;
	else
		ss << constant_;

	string result = "C" + ss.str();
	ConstantValueTable_[result]=value;
	constant_++;
	return result;
}

// Get the next available temporary and then increment it
// If temporary_ = 4, then this would return T4 and then 
// the next call would return T5, and so on.
string RAMCounter::getNextTemporary() 
{
	stringstream ss;
	if(temporary_ < 10)
		ss << "0" << temporary_;
	else
		ss << temporary_;
	
	string result = "T" + ss.str();
	temporary_++;
	return result;
}

// Get the next available label and then increment it
// If label_ = 4, then this would return L4 and then 
// the next call would return L5, and so on.
string RAMCounter::getNextLabel() 
{
	stringstream ss;
	if(label_ < 10)
		ss << "0" << label_;
	else
		ss << label_;

	string result = "L" + ss.str();
	label_++;
	return result;
}

// Adds an instruction to RAMCounter
void RAMCounter::addInstruction(Operators oper, string operand, Types type) 
{
	RAMInstruction ri(oper, operand, this->lineCounter_, type);
	this->instructions_.push_back(ri);
	lineCounter_++;
} 

// Memory
// Dumps the content of the tables to help build the
// Memory file for input into RAM
void RAMCounter::memory()
{
	map<string,int>::iterator mapiter;
	map<string,int>::iterator valiter;

	for (mapiter = constantTable_.begin();mapiter != constantTable_.end();mapiter++) 
    {
		valiter = ConstantValueTable_.find(mapiter->first);
    	cout << mapiter->second << " " << valiter->second << " ;" << mapiter->first << endl;
	}
  	for (mapiter = localTable_.begin();mapiter != localTable_.end();mapiter++)
    	cout << mapiter->second << " 0 ;" << mapiter->first << endl;
  	for (mapiter = tempTable_.begin();mapiter != tempTable_.end();mapiter++)
    	cout << mapiter->second << " 0 ;" << mapiter->first << endl;
}

// Linker
// Iterate over the instructions
// Assign, in order the CONSTANT variables, to memory locations
// Assign, in order the LOCAL variables, to memory locations
// Assign, in order the TEMP variables, to memory locations
// Remove the label markers
// Replace the JMN and JMZ labels to be the line number of the labels
// Assign the resulting operands to the linked property of RAM instruction
void RAMCounter::link()
{
	list<RAMInstruction>::iterator p;
	map<string,int>::iterator mapiter;
	int allCounter;
	
	allCounter = 1;
	// Go through all the instructions and create the map
	// of constants
	for (p = instructions_.begin();p != instructions_.end();p++) 
	{
		if(p->getType() == CONST) 
		{
			if(this->constantTable_.find(p->getOperand()) == constantTable_.end()) 
			{
				this->constantTable_[p->getOperand()] = allCounter;
				allCounter++;
			}	
		}
	}
	
	// Now go through and build a list of the local variables
 	for (p = instructions_.begin();p != instructions_.end();p++) 
  	{
  		if(p->getType() == LOCAL) 
  		{
  			if(this->localTable_.find(p->getOperand()) == localTable_.end()) 
  			{
  				this->localTable_[p->getOperand()] = allCounter;
  				allCounter++;
  			}
  		}
  	}
  	
  	// Next go through and build a list of temporaries
  	for (p = instructions_.begin();p != instructions_.end();p++) 
  	{
  		if(p->getType() == TEMP) 
  		{
  			if(this->tempTable_.find(p->getOperand()) == tempTable_.end()) 
  			{
  				this->tempTable_[p->getOperand()] = allCounter;
  				allCounter++;
  			}
  		}
  	}
  	
  	// And now set the labels
  	for (p = instructions_.begin();p != instructions_.end();p++) 
  	{
  		if(p->getOper() == LBL && p->getType() == LABEL) 
  		{
  			if(this->labelTable_.find(p->getOperand()) == labelTable_.end()) 
  			{
  				this->labelTable_[p->getOperand()] = p->getLineNumber();
  			}
  		}
  	}

	// Remove the labels, they are no longer needed
	for (p=instructions_.begin(); p!=instructions_.end();p++)
	{
		if (p->getOper()==LBL && p->getType()==LABEL)
		{
			p=instructions_.erase(p);
		}
	}
	
	for (p = instructions_.begin();p != instructions_.end();p++) 
	{
		// Convert LD to LDA and ST to STA for constants
		// Set memory location as absolute location
		if(p->getType()==CONST) 
		{
			if(p->getOper()==LD)
				p->setLinked(LDA,constantTable_[p->getOperand()]);
			else if(p->getOper()==ST)	
				p->setLinked(STA,constantTable_[p->getOperand()]);
			else
				p->setLinked(p->getOper(),constantTable_[p->getOperand()]);
		}
		// Convert LD to LDA and ST to STA for local variables
		// Set memory location as a relative pointer
		else if(p->getType()==LOCAL)
		{
			if(p->getOper()==LD)
				p->setLinked(LDA,localTable_[p->getOperand()]);
			else if(p->getOper()==ST)	
				p->setLinked(STA,localTable_[p->getOperand()]);
			else
				p->setLinked(p->getOper(),localTable_[p->getOperand()]);
		}
		// Convert LD to LDA and ST to STA for temporary variables
		// Set memory location as absolute
		else if( p->getType()==TEMP) 
		{
			if(p->getOper()==LD)
				p->setLinked(LDA,tempTable_[p->getOperand()]);
			else if(p->getOper()==ST)	
				p->setLinked(STA,tempTable_[p->getOperand()]);
			else
				p->setLinked(p->getOper(),tempTable_[p->getOperand()]);
		}
		// Replace the label locations with hard coded memory locations
 		else if(p->getType()==LABEL)
		{
			p->setLinked(p->getOper(),labelTable_[p->getOperand()]);
		}	
		// Set HLT
 		else if(p->getOper()==HLT)
		{
			p->setLinked(p->getOper(),0);
		}	
	}		
}

void RAMCounter::dump() 
{
	list<RAMInstruction>::iterator p;

	for (p = instructions_.begin();p != instructions_.end();p++) 
	{
		if(p->getOper()==HLT)
		{
			cout<< p->getLinkedOperString() << " ; "<< p->getLineNumber() << " " << p->getOperString() << " " << p->getOperand() << endl;
		}
		else 
		{
			cout<< p->getLinkedOperString() << " " << p->getLinkedOperand() << " ; "<< p->getLineNumber() << " " << p->getOperString() << " " << p->getOperand() << endl;
		}
	}
	
}

// Adds a label to RAMCounter
void RAMCounter::addLabel(string lbl) 
{
	RAMInstruction ri(LBL, lbl, this->lineCounter_, LABEL);
	this->instructions_.push_back(ri);
} 

// Peephole optimization to remove redundant LD statements
void RAMCounter::optimize() 
{
	list<RAMInstruction>::iterator p;
	for (p=instructions_.begin(),p++; p!=instructions_.end();p++) 
		if (p->getOperString()=="LD")
		{
			string op = p->getOperand();
			advance(p,-1);
			if (p->getOperString()=="ST" && p->getOperand()==op)
			{
				advance(p,1);
				p=instructions_.erase(p);
			}
			else
				advance(p,1);
		}

	int n=1;
	for (p=instructions_.begin(); p!=instructions_.end();p++)
	{
		p->setLineNumber(n);
		if (p->getOperString()!="LBL")
			n++;
	}
} 


Program::Program(StmtList *SL, bool optimizer_)
{
	SymbolTable_.clear();
	SL_ = SL;
	optimizer = optimizer_;
}

void Program::dump() 
{
    RAMCounter_.dump();
}

void Program::eval() 
{
	SL_->eval(SymbolTable_);
}

// Compile method runs translate
// Optionally runs the optimizer
// Then runs the linker
void Program::compile()
{
	this->translate();
	if(optimizer==true)
		RAMCounter_.optimize();
	RAMCounter_.link();
}

void Program::memory()
{
	RAMCounter_.memory();
}

// Translate the program into RAL
// Example: a := 3, a :=2; b := 3, and so on
// Simply call the translate function for the list of statements
void Program::translate()
{
	SL_->translate(SymbolTable_ , RAMCounter_);
	RAMCounter_.addInstruction(HLT,"",UNDEFINED);
}

void StmtList::insert(Stmt * S)
{
  SL_.push_front(S);
}

void StmtList::eval(map<string,int> &T) 
{
  list<Stmt*>::iterator Sp;
  for (Sp = SL_.begin();Sp != SL_.end();Sp++)
	(*Sp)->eval(T);
}

// Translate this list of statements into RAL
// Examples: a := 3 ; if a then b:=3 else b:=2 fi; a := b - a ; a := a - 1, and so on
// stmt_list:  stmt ';' stmt_list |  stmt
// Iterate over the list and process each one
void StmtList::translate(map<string,int> &T, RAMCounter &rc) 
{
	//cout << "StmtList" << endl;
  	list<Stmt*>::iterator Sp;
  	for (Sp = SL_.begin();Sp != SL_.end();Sp++)
		(*Sp)->translate(T,rc);
}

AssignStmt::AssignStmt(string name, Expr *E)
{
  name_ = name;
  E_ = E;
}

void AssignStmt::eval(map<string,int> &T) const
{
	T[name_] = E_->eval(T);
}


// Translate this assignment statement into RAL
// Examples: a := 3, a := b - a, a := a - 1, and so on
// assign_stmt: IDENT ASSIGNOP expr
// The value of expr is stored in temporary variable temp1
// Load temp1 into the accumulator
// Store the accumulator to the value at the identifier
void AssignStmt::translate(map<string,int> &t, RAMCounter &rc) const
{
	string temp1;
	
	//cout << "Assign" << endl;
	temp1 = E_->translate( t , rc );
//	cout << "LD " << temp1 << endl;
	rc.addInstruction(LD, temp1,TEMP);
//	cout << "ST " << name_ << endl;
	rc.addInstruction(ST, name_, LOCAL);
}

IfStmt::IfStmt(Expr *E, StmtList *S1, StmtList *S2)
{
  E_ = E;
  S1_ = S1;
  S2_ = S2;
}

void IfStmt::eval(map<string,int> &T) const
{
	if (E_->eval(T) > 0)
		S1_->eval(T);
	else
		S2_->eval(T);
}

// Translate this IF statement into RAL
// Examples: if b-a then a := b - a else a := a - b fi, and so on
// if_stmt: IF expr THEN stmt_list ELSE stmt_list FI
// Get the next available label into label1
// Set label1 at the point where the else statement list begins
// Get another label into label2
// Set label2 at the statement at the end of the else
// The value of expr is stored in temporary variable temp1
// Load temp1 into the accumulator
// If the accumulator is negative, jump to label1
// If the accumulator is positive, jump to label1
// Otherwise, evaluate the statements in the then condition
// After processing the then condition, jump to the label2
void IfStmt::translate(map<string,int> &T, RAMCounter &rc) const
{
	string temp1, label1, label2;	

	temp1 = E_->translate(T,rc);
	label1 = rc.getNextLabel();
	label2 = rc.getNextLabel();
	rc.addInstruction(LD, temp1, TEMP);
	rc.addInstruction(JMN, label1, LABEL);
	rc.addInstruction(JMZ, label1, LABEL);
	S1_->translate(T,rc);
	rc.addInstruction(JMP, label2, LABEL);
	rc.addLabel(label1);
	S2_->translate(T,rc);
	rc.addLabel(label2);
}

WhileStmt::WhileStmt(Expr *E, StmtList *S)
{
  E_ = E;
  S_ = S;
}

void WhileStmt::eval(map<string,int> &T) const
{
	while (E_->eval(T) > 0) 
		S_->eval(T);
}

// Translate this While statement into RAL
// Examples: while a do a := a -1 od, while b-c do b := b + 1; c := c - 1 od, and so on
// while_stmt : WHILE expr DO stmt_list OD
// Get the next available label into label1
// Set label1 at the point where expr is calculated
// Get another label into label2
// Set label2 at the statement at the end of the loop
// The value of expr is stored in temporary variable temp1
// Load temp1 into the accumulator
// If the accumulator is negative, jump to label2
// If the accumulator is positive, jump to label2
// Otherwise, evaluate the statements in the body of the loop
// Then jump back to the top of the loop
void WhileStmt::translate(map<string,int> &T, RAMCounter &rc) const
{
	string temp1, label1, label2;

	label1 = rc.getNextLabel();
	label2 = rc.getNextLabel();
	
	rc.addLabel(label1);
	temp1 = E_->translate(T,rc);
	rc.addInstruction(LD, temp1, TEMP);
	rc.addInstruction(JMN, label2, LABEL);
	rc.addInstruction(JMZ, label2, LABEL);
	S_->translate(T,rc);
	rc.addInstruction(JMP, label1, LABEL);
	rc.addLabel(label2);
}

Number::Number(int value)
{
	value_ = value;
}

int Number::eval(map<string,int> T) const
{
	return value_;
}

// Translate this number into RAL
// Examples: 17, 44, 23, 0, and so on
// factor (result) : IDENT (expr1)
// Expr1 is a constant
// Load expr1 into the accumulator
// Store the accumulator into temp1, the next available temp
// Return the name of temp1 to the calling program
string Number::translate(map<string,int> t, RAMCounter &rc) const
{

	string con, temp1;

	con = rc.getNextConstant(value_);
	temp1 = rc.getNextTemporary();
	rc.addInstruction(LD, con, CONST);
	rc.addInstruction(ST, temp1, TEMP);
	return temp1;
}

Ident::Ident(string name)
{
	name_ = name;
}

int Ident::eval(map<string,int> T) const
{
	return T[name_];
}

// Translate this identifier into RAL
// Examples: A, B, price, result, and so on
// factor (result) : IDENT (expr1)
// Expr1 is not in a temporary variable
// Load expr1 into the accumulator
// Store the accumulator into temp1, the next available temp
// Return the name of temp1 to the calling program
string Ident::translate(map<string,int> t, RAMCounter &rc) const
{
	
	string temp1;

	temp1 = rc.getNextTemporary();
	rc.addInstruction(LD, name_, LOCAL);
	rc.addInstruction(ST, temp1, TEMP);
	return temp1;
}

Plus::Plus(Expr* op1, Expr* op2)
{
	op1_ = op1;
	op2_ = op2;
}

int Plus::eval(map<string,int> T) const
{
	return op1_->eval(T) + op2_->eval(T);
}

// Translate this addition operation into RAL
// Examples: 3 + 5, 2 + a, i + j
// expr (result) : expr (expr1) + term (expr2)
// Temporary variable of expr1 is saved in temp1
// Temporary variable of expr2 is saved in temp2
// Load temp1 into the accumulator
// Apply operation from temp2 to the accumulator
// Store the accumulator into temp3, the next available temp
// Return the name of temp3 to the calling program
string Plus::translate(map<string,int> t , RAMCounter &rc ) const
{
	string temp1, temp2, temp3;

	temp1 = op1_->translate(t , rc);
	temp2 = op2_->translate(t , rc);
	temp3 = rc.getNextTemporary();
	rc.addInstruction(LD, temp1, TEMP);
	rc.addInstruction(ADD, temp2, TEMP);
	rc.addInstruction(ST, temp3, TEMP);
	return temp3;
}

Minus::Minus(Expr* op1, Expr* op2)
{
	op1_ = op1;
	op2_ = op2;
}

int Minus::eval(map<string,int> T) const
{
	return op1_->eval(T) - op2_->eval(T);
}

// Translate this subtraction operation into RAL
// Examples: 3 - 2, 10 - k, after - before, and so on
// expr (result) : expr (expr1) - term (expr2)
// Temporary variable of expr1 is saved in temp1
// Temporary variable of expr2 is saved in temp2
// Load temp1 into the accumulator
// Apply operation from temp2 to the accumulator
// Store the accumulator into temp3, the next available temp
// Return the name of temp3 to the calling program
string Minus::translate(map<string,int> t , RAMCounter &rc ) const
{
	string temp1, temp2, temp3;

	//cout << "Minus" << endl;
	temp1 = op1_->translate(t , rc);
	temp2 = op2_->translate(t , rc);
	temp3 = rc.getNextTemporary();
	rc.addInstruction(LD, temp1, TEMP);
	rc.addInstruction(SUB, temp2, TEMP);
	rc.addInstruction(ST, temp3, TEMP);
	return temp3;
}

Times::Times(Expr* op1, Expr* op2)
{
	op1_ = op1;
	op2_ = op2;
}

int Times::eval(map<string,int> T) const
{
	return op1_->eval(T) * op2_->eval(T);
}

// Translate this multiplation operation into RAL
// Examples: 4 * 2, r * 7, width * height
// term (result) : term (expr1) * factor (expr2)
// Temporary variable of expr1 is saved in temp1
// Temporary variable of expr2 is saved in temp2
// Load temp1 into the accumulator
// Apply operation from temp2 to the accumulator
// Store the accumulator into temp3, the next available temp
// Return the name of temp3 to the calling program
string Times::translate(map<string,int> t , RAMCounter &rc ) const
{
	string temp1, temp2, temp3;
	temp1 = op1_->translate(t , rc);
	temp2 = op2_->translate(t , rc);
	temp3 = rc.getNextTemporary();
	rc.addInstruction(LD, temp1, TEMP);
	rc.addInstruction(MUL, temp2, TEMP);
	rc.addInstruction(ST, temp3, TEMP);
	return temp3;
}

